package city.spring.configure.resource;

import city.spring.configure.security.handler.CustomLogoutRequestMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.expression.OAuth2WebSecurityExpressionHandler;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;

import javax.annotation.PostConstruct;

/**
 * 资源配置服务
 *
 * @author HouKunLin
 * @date 2019/12/3 0003 22:23
 */
@Configuration
@EnableResourceServer
public class CustomResourceServerConfiguration extends ResourceServerConfigurerAdapter {
    private static final Logger logger = LoggerFactory.getLogger(CustomResourceServerConfiguration.class);
    @Autowired
    private AccessDeniedHandler accessDeniedHandler;
    @Autowired
    private AuthenticationEntryPoint authenticationEntryPoint;
    @Autowired
    private ResourceServerProperties resourceServerProperties;
    @Autowired
    private CustomLogoutRequestMatcher logoutRequestMatcher;
    @Autowired
    private GrantedAuthorityDefaults grantedAuthorityDefaults;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        // 默认使用 OAuth2 类型的异常信息输出，如果想要自定义异常错误输出格式需要在这里设置
        // 使用OAuth2进行授权的时候，当权限不足的时候，因权限不足不允许访问的时候，异常信息走这里设置的对象来处理
        resources.accessDeniedHandler(accessDeniedHandler);
        // 当未登录时（无Token），因未登录无法访问系统的时候，异常信息不会走这里的设置对象来处理，而是直接走安全程序设置的方式处理
        // 当Token失效的时候将使用这里配置的处理器来处理异常信息并响应给前端
        resources.authenticationEntryPoint(authenticationEntryPoint);
        // 设置当前应用的资源ID，该ID必须包含在客户端的resourceIds列表中，否则这个客户端分发的Token不能访问当前服务
        // 这样可以限制某个客户端分发的Token信息只能访问指定资源服务
        resources.resourceId(resourceServerProperties.getResourceId());
        // 不能设置这个，因为设置这个后，无法使用客户端模式访问系统（能获取Token，但是不能使用Token）
        // resources.authenticationManager(authenticationManager);
        // 设置默认的角色前缀信息
        OAuth2WebSecurityExpressionHandler expressionHandler = new OAuth2WebSecurityExpressionHandler();
        expressionHandler.setDefaultRolePrefix(grantedAuthorityDefaults.getRolePrefix());
        resources.expressionHandler(expressionHandler);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.cors().disable();
        // http.exceptionHandling()
        // 使用OAuth2进行授权的时候，当权限不足的时候，因权限不足不允许访问的时候，异常信息走这里设置的对象来处理
        // .accessDeniedHandler(accessDeniedHandler)
        // 当未登录时，因未登录无法访问系统的时候，异常信息不会走这里的设置对象来处理，而是直接走安全程序设置的方式处理
        // .authenticationEntryPoint(authenticationEntryPoint)
        // 决定哪些请求经过资源服务认证处理（结果为true时），当结果为false时不经过资源程序处理，直接进入到下一步安全程序处理流程
        // 忽略退出登录请求，使退出登录请求交给安全程序处理
        http.requestMatcher(new OAuth2RequestedMatcher(logoutRequestMatcher))
                .authorizeRequests()
                .anyRequest()
                .permitAll();
    }

    @PostConstruct
    public void postConstruct() {
        logger.debug("自定义资源服务器配置: {}", this);
    }
}
